﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HSDc.RingleFlow.Api.Kernel;
using HSDc.RingleFlow.Kernel.Persistence;
using HSDc.RingleFlow.Kernel.ProcessActivity;

namespace HSDc.RingleFlow.Kernel
{
    class ProcessInstance: IProcessInstance
    {
        private string _id;
        private ExecutionState _state;
        private Dictionary<string, object> _variables = new Dictionary<string,object>();
        private ProcessDefinition definition;
        private List<Activity> currentActivities = new List<Activity>();
        private List<ActivityInstance> finishedTasks = new List<ActivityInstance>();
        private Dictionary<string, ActivityInstance> waitTasks = new Dictionary<string, ActivityInstance>();
        private Dictionary<string, ActivityInstance> currentTasks = new Dictionary<string, ActivityInstance>();
        private WFPersitable persist = null;

        /// <summary>
        /// For Retrieve Data From DB
        /// </summary>
        /// <param name="_id"></param>
        /// <param name="processDefinition"></param>
        /// <param name="currentTasks"></param>
        /// <param name="completedTasks"></param>
        /// <param name="variables"></param>
        internal ProcessInstance(string _id, ProcessDefinition processDefinition, List<ActivityInstance> currentTasks, List<ActivityInstance> waitTasks,
            List<ActivityInstance> finishedTasks, Dictionary<string, object> variables)
        {
            this._id = _id;
            foreach (ActivityInstance ai in currentTasks)
                this.currentTasks.Add(ai.Id, ai);
            foreach (ActivityInstance ai in waitTasks)
                this.waitTasks.Add(ai.ActivityDefinitionId, ai);
            this.finishedTasks = finishedTasks;
            this._variables = variables;
            this.definition = processDefinition;
            foreach (ActivityInstance ai in this.currentTasks.Values)
                ai.SetProcessInstance(this);
        }

        /// <summary>
        /// For Retrieve Data From DB
        /// </summary>
        /// <param name="_id"></param>
        /// <param name="processDefinition"></param>
        /// <param name="currentTasks"></param>
        /// <param name="variables"></param>
        internal ProcessInstance(string _id, ProcessDefinition processDefinition, List<ActivityInstance> currentTasks, 
            Dictionary<string, object> variables) : this(_id, processDefinition, currentTasks, null, null, variables)
        {
            this.waitTasks = new Dictionary<string, ActivityInstance>();
            this.finishedTasks = new List<ActivityInstance>();
        }

        /// <summary>
        /// For New Process Instance
        /// </summary>
        /// <param name="_id"></param>
        /// <param name="processDefinition"></param>
        internal ProcessInstance(string _id, ProcessDefinition processDefinition):this(_id, processDefinition, null)
        {
        }

        /// <summary>
        /// For New Process Instance
        /// </summary>
        /// <param name="_id"></param>
        /// <param name="processDefinition"></param>
        /// <param name="variables"></param>
        internal ProcessInstance(string _id, ProcessDefinition processDefinition, Dictionary<string, object> variables)
        {
            this._id = _id;
            this.definition = processDefinition;
            this._variables = variables;
            currentActivities.Clear();
            Activity current = definition.GetFirstActivity();
            currentActivities.Add(current);
            string acId = Guid.NewGuid().ToString();
            currentTasks.Add(acId, new ActivityInstance(acId, current, "", this));
            _state = ExecutionState.Active;
        }
        string IProcessInstance.Id
        {
            get { return _id; }
        }

        ExecutionState IProcessInstance.CurrentState
        {
            get { return _state; }
        }

        IProcessDefinition IProcessInstance.Definition
        {
            get { return definition; }
        }

        Dictionary<string, object> IProcessInstance.GetVaraibles()
        {
            return _variables;
        }

        object IProcessInstance.GetVaraible(string variableName)
        {
            object value = null;
            _variables.TryGetValue(variableName, out value);
            return value;
        }

        List<string> IProcessInstance.GetAllActiveActivityNames()
        {
            throw new NotImplementedException();
        }

        void IProcessInstance.AddVaraible(string VariableName, object Varaible)
        {
            if (_variables.ContainsKey(VariableName))
                _variables.Remove(VariableName);
            _variables.Add(VariableName, Varaible);
        }

        internal ActivityInstance FindActivity(string ActivityId)
        {
            ActivityInstance ai = null;
            if (currentTasks.ContainsKey(ActivityId))
                currentTasks.TryGetValue(ActivityId, out ai);
            return ai;
        }

        internal void SetVariables(Dictionary<string, object> value)
        {
            _variables = value;
        }

        internal void FinishActivity(string ActivityId, string TaskId, Dictionary<string, object> variables)
        {
            Dictionary<string, object> newVariable = SetupRuleVariables(variables);
            ActivityInstance current = FindActivity(TaskId);
            if (HasWaitTasks(current))
                return;
            CleanCurrentTasks(TaskId);
            currentActivities.Clear();
            currentActivities = definition.NextActivities(ActivityId, newVariable);
            AddCurrentTasks(variables);
            SaveThis();
            CheckAutomaticActivity();
        }

        private void SaveThis()
        {
            persist = PersistHelper.GetPersist("kernel/ProcessInstance");
            persist.Save(this);
        }

        private void CheckAutomaticActivity()
        {
            List<ActivityInstance> tmpCurrent = new List<ActivityInstance>();
            tmpCurrent.AddRange(currentTasks.Values.ToArray());
            foreach (ActivityInstance current in tmpCurrent)
            {
                if (current.ActivityDefinitionType == typeof(EndActivity))
                    Complete();
                else if (current.CanExecute)
                {
                    FinishActivity(current.ActivityDefinitionId, current.Id, current.Variables);
                }
            }
        }

        private bool HasWaitTasks(ActivityInstance current)
        {
            bool result = true;
            if (current.ActivityDefinitionType == typeof(JoinActivity))
            {
                result = (currentTasks.Count == 1);
                if (result)
                {
                    currentTasks.Remove(current.Id);
                    current.SetState(TaskState.Completed);
                    currentTasks.Add(current.Id, current);
                    SaveThis();
                }
            }
            return !result;
        }

        private void AddCurrentTasks(Dictionary<string, object> variables)
        {
            foreach (Activity ac in currentActivities)
            {
                string id = Guid.NewGuid().ToString();
                ActivityInstance ai = new ActivityInstance(id, ac, "", this);
                if (ac.CanExecute)
                {
                    ai.Variables = variables;
                    if (waitTasks.ContainsKey(ac.Id))
                        waitTasks.TryGetValue(ac.Id, out ai);
                    else if (ac.GetType() == typeof(JoinActivity))
                        ai.SetState(TaskState.Wait);
                    else
                        ai.SetState(TaskState.Completed);
                }
                currentTasks.Add(ai.Id, ai);
            }
        }

        private void CleanCurrentTasks(string TaskId)
        {
            ActivityInstance[] tmpActivity = new ActivityInstance[currentTasks.Count];
            currentTasks.Values.ToList().CopyTo(tmpActivity);
            foreach (ActivityInstance fi in tmpActivity)
            {
                if (fi.Id.Equals(TaskId))
                {
                    currentTasks.Remove(fi.Id);
                    if (((ITask)fi).CurrentState == TaskState.Wait)
                    {
                        if (waitTasks.ContainsKey(fi.ActivityDefinitionId))
                            waitTasks.Remove(fi.ActivityDefinitionId);
                        waitTasks.Add(fi.ActivityDefinitionId, fi);
                    }
                    else
                        finishedTasks.Add(fi);
                }
            }
        }

        private Dictionary<string, object> SetupRuleVariables(Dictionary<string, object> variables)
        {
            Dictionary<string, object> newVariable = new Dictionary<string, object>();
            object varValue = null;
            foreach (string key in _variables.Keys)
            {
                _variables.TryGetValue(key, out varValue);
                newVariable.Add(key, varValue);
            }
            foreach (string key in variables.Keys)
            {
                variables.TryGetValue(key, out varValue);
                newVariable.Add(key, varValue);
            }
            return newVariable;
        }

        private void Complete()
        {
            _state = ExecutionState.Ended;
            persist = PersistHelper.GetPersist("kernel/CompleteProcessIntance");
            persist.Save(this);
        }

        internal ActivityInstance GetCurrentTask(string Assignee)
        {
            foreach (ActivityInstance ac in currentTasks.Values)
                if (ac.ContainsAssignee(Assignee))
                    return ac;
            return null;
        }

        internal List<ActivityInstance> GetCurrentTasks()
        {
            return currentTasks.Values.ToList();
        }

        internal void ForceComplete()
        {
            Complete();
        }
    }
}
